#include <assert.h>
#include <stdlib.h>

#include "base/simapi.h"
#include "util/ticket.h"

#include "config.h"
#ifdef WITH_TM
# include "tm/tmapi.h"
#endif
//#define SIM_THREAD_DEBUG

// A processor has the ticket if ticketCPU == Sim_GetMyId().  This
// grants them the right to switch to real mode and fast forward,
// blocking all other threads.  Once processor zero (boot) has
// finished it sets sharedTicketAllowed to true and then any
// processor that wants a ticket can have one.  Before that they
// must proceed in a round-robin fashion, using numVirtualProcessors
// to coordinate.
static volatile int scheduledCPU = 0;
static volatile bool sharedTicketAllowed = false;

void checkpoint()
{
    if (Sim_GetMode() == MODE_TM) {
#ifdef WITH_TM
        TM_BeginClosed();
        TM_EndClosed();
#else
        Sim_Print0("Somehow mode is MODE_TM but WITH_TM was not defined.");
        exit(1);
#endif
    }
}

void Ticket_Wait()
{
    checkpoint();

#ifdef SIM_THREAD_DEBUG
    Sim_Print1("[Ticket] cpu[%d] waiting for ticket\n", Sim_GetMyId());
#endif // SIM_THREAD_DEBUG
    int const myNum = Sim_GetMyId();
    while (scheduledCPU != myNum && !sharedTicketAllowed) {
        ;
    }
#ifdef SIM_THREAD_DEBUG
    if (sharedTicketAllowed) {
        Sim_Print1("[Ticket] cpu[%d] ticket is shared\n", Sim_GetMyId());
    }
    else {
        Sim_Print1("[Ticket] cpu[%d] obtained exclusive ticket\n", Sim_GetMyId());
    }
#endif // SIM_THREAD_DEBUG

    checkpoint();
}

void Ticket_Yield(int const numVP)
{
    checkpoint();

    assert(scheduledCPU >= 0 && scheduledCPU < numVP);

    if (++scheduledCPU == numVP) {
        scheduledCPU = 0;
    }

#ifdef SIM_THREAD_DEBUG
    Sim_Print2("[Ticket] cpu[%d] yielded ticket to cpu[%d]\n", Sim_GetMyId(), scheduledCPU);
#endif // SIM_THREAD_DEBUG

    checkpoint();

    // wait until we get it back
    Ticket_Wait();
}

void Ticket_EnableSharedTicket()
{
    checkpoint();

    sharedTicketAllowed = true;
#ifdef SIM_THREAD_DEBUG
    Sim_Print1("[Ticket] ticket sharing enabled by processor %d\n", Sim_GetMyId());
#endif // SIM_THREAD_DEBUG

    checkpoint();
}
